{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "<center>\n",
    "<img src=\"https://raw.githubusercontent.com/Yorko/mlcourse.ai/master/img/ods_stickers.jpg\" />\n",
    "    \n",
    "## [mlcourse.ai](https://mlcourse.ai) - Open Machine Learning Course\n",
    "\n",
    "<center>\n",
    "Auteur: [Yury Kashnitskiy](https://yorko.github.io).  \n",
    "Traduit et édité par [Serge Oreshkov](https://www.linkedin.com/in/sergeoreshkov/), [Yuanyuan Pao](https://www.linkedin.com/in/yuanyuanpao/) et [Ousmane Cissé](https://www.linkedin.com/in/ousmane-ciss%C3%A9/).  \n",
    "Ce matériel est soumis aux termes et conditions de la licence [Creative Commons CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/).  \n",
    "L'utilisation gratuite est autorisée à des fins non commerciales.\n",
    "\n",
    "# <center> Topic 8. Vowpal Wabbit: apprendre avec des gigaoctets de données\n",
    "Cette semaine, nous aborderons deux raisons qui expliquent la vitesse d'entraînement exceptionnelle de Vowpal Wabbit, à savoir l'apprentissage en ligne et le hashing trick, tant en théorie qu'en pratique. Nous allons l'essayer avec des articles d'actualités, des critiques de films et des questions sur le StackOverflow."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "# Plan de l'article\n",
    "1. [Stochastic gradient descent and online learning](#1.-Stochastic-gradient-descent-and-online-learning)\n",
    "    - 1.1. [SGD](#1.1.-Stochastic-gradient-descent)\n",
    "    - 1.2. [Online approach to learning](#1.2.-Online-approach-to-learning)\n",
    "2. [Categorical feature processing](#2.-Categorical-feature-processing)\n",
    "    - 2.1. [Label Encoding](#2.1.-Label-Encoding)\n",
    "    - 2.2. [One-Hot Encoding](#2.2.-One-Hot-Encoding)\n",
    "    - 2.3. [Hashing trick](#2.3.-Hashing-trick)\n",
    "3. [Vowpal Wabbit](#3.-Vowpal-Wabbit)\n",
    "    - 3.1. [News. Binary classification](#3.1.-News.-Binary-classification)\n",
    "    - 3.2. [News. Multiclass classification](#3.2.-News.-Multiclass-classification)\n",
    "    - 3.3. [IMDB movie reviews](#3.3.-IMDB-movie-reviews)\n",
    "    - 3.4. [Classifying gigabytes of StackOverflow questions](#3.4.-Classifying-gigabytes-of-StackOverflow-questions)\n",
    "4. [Demo assignment](#4.-Demo-assignment)\n",
    "5. [Useful resources](#5.-Useful-resources)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "64efb6ef-3591-4036-8461-b7b467f404b7",
    "_uuid": "bd1f11458028d021571d4a77e3de846bf0dee992"
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import re\n",
    "import warnings\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from scipy.sparse import csr_matrix\n",
    "from sklearn.datasets import fetch_20newsgroups, load_files\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.metrics import (\n",
    "    accuracy_score,\n",
    "    classification_report,\n",
    "    confusion_matrix,\n",
    "    log_loss,\n",
    "    roc_auc_score,\n",
    "    roc_curve,\n",
    ")\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import LabelEncoder, OneHotEncoder\n",
    "from tqdm import tqdm_notebook\n",
    "\n",
    "%matplotlib inline\n",
    "import seaborn as sns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "warnings.filterwarnings(\"ignore\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 1. Descente de gradient stochastique et apprentissage en ligne\n",
    "### 1.1. Descente de gradient stochastique\n",
    "\n",
    "Bien que la descente de gradient soit l'une des premières choses apprises dans les cours d'apprentissage automatique et d'optimisation, c'est l'une de ses modifications, la descente de gradient stochastique (SGD), qui est difficile à surmonter.\n",
    "\n",
    "Rappelons que l'idée de la descente en gradient est de minimiser certaines fonctions en faisant de petits pas dans le sens de la décroissance la plus rapide. Cette méthode a été nommée en raison du fait suivant du calcul: le vecteur $\\nabla f = (\\frac{\\partial f}{\\partial x_1}, \\ldots \\frac{\\partial f}{\\partial x_n})^\\text{T}$ des dérivées partielles de la fonction $f(x) = f(x_1, \\ldots x_n)$ pointe vers la direction de la croissance de la fonction la plus rapide. Cela signifie qu'en se déplaçant dans la direction opposée (antigradient), il est possible de diminuer la valeur de la fonction avec le taux le plus rapide.\n",
    "\n",
    "<img src='https://habrastorage.org/files/4f2/75d/a46/4f275da467a44fc4a8d1a11007776ed2.jpg' width=50%>\n",
    "\n",
    "Voici un snowboarder (moi) à Sheregesh, la station d'hiver la plus populaire de Russie. (Je le recommande vivement si vous aimez le ski ou le snowboard). En plus de faire la publicité des beaux paysages, cette image dépeint l'idée d'une descente en pente. Si vous voulez rouler le plus vite possible, vous devez choisir le chemin de descente le plus raide. Le calcul des antigradients peut être considéré comme une évaluation de la pente à divers endroits."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "**Exemple**\n",
    "\n",
    "Le problème de la régression par paires peut être résolu par la descente en pente. Prévoyons une variable à l'aide d'une autre : la taille avec le poids. Supposons que ces variables soient linéairement dépendantes. Nous utiliserons l'ensemble de données [SOCR](http://wiki.stat.ucla.edu/socr/index.php/SOCR_Data)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "84022022-7ce2-4fac-90ae-3cefc5a12961",
    "_uuid": "0873cf8a28158f2ebdd5b3b42d3026efcd8e6b58"
   },
   "outputs": [],
   "source": [
    "PATH_TO_ALL_DATA = \"../../data/\"\n",
    "data_demo = pd.read_csv(os.path.join(PATH_TO_ALL_DATA, \"weights_heights.csv\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "094aa0fc-8af4-4a13-88a7-4eedf53fd712",
    "_uuid": "a60148fd610f423b517a38dad8768f0f6c76d192"
   },
   "outputs": [],
   "source": [
    "plt.scatter(data_demo[\"Weight\"], data_demo[\"Height\"])\n",
    "plt.xlabel(\"Weight in lb\")\n",
    "plt.ylabel(\"Height in inches\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous avons un vecteur $x$ de dimension $\\ell$ (poids de chaque personne, c'est-à-dire un échantillon d'entraînement) et $y$, un vecteur contenant la taille de chaque personne dans l'ensemble de données.\n",
    "\n",
    "La tâche est la suivante: trouver les poids $w_0$ et $w_1$ de telle sorte que la prédiction de la hauteur comme $y_i = w_0 + w_1 x_i$ (où $y_i$ est la valeur de hauteur $i$-th, $x_i$ est la valeur de poids $i$-th) minimise l'erreur quadratique (ainsi que l'erreur quadratique moyenne car $\\frac{1}{\\ell}$ ne fait aucune différence):\n",
    "$$SE(w_0, w_1) = \\frac{1}{2}\\sum_{i=1}^\\ell(y_i - (w_0 + w_1x_{i}))^2 \\rightarrow min_{w_0,w_1}$$\n",
    "\n",
    "Nous utiliserons la descente de gradient, en utilisant les dérivées partielles de $SE(w_0, w_1)$ sur les poids $w_0$ et $w_1$.\n",
    "Une procédure d'apprentissage itérative est alors définie par de simples formules de mise à jour (on modifie les poids du modèle par petits pas, proportionnels à une petite constante $\\eta$, vers l'antigradient de la fonction $SE(w_0, w_1)$):\n",
    "\n",
    "$$\\begin{array}{rcl} w_0^{(t+1)} = w_0^{(t)} -\\eta \\frac{\\partial SE}{\\partial w_0} |_{t} \\\\  w_1^{(t+1)} = w_1^{(t)} -\\eta \\frac{\\partial SE}{\\partial w_1} |_{t} \\end{array}$$\n",
    "\n",
    "En calculant les dérivées partielles, nous obtenons ce qui suit:\n",
    "\n",
    "$$\\begin{array}{rcl} w_0^{(t+1)} = w_0^{(t)} + \\eta \\sum_{i=1}^{\\ell}(y_i - w_0^{(t)} - w_1^{(t)}x_i) \\\\  w_1^{(t+1)} = w_1^{(t)} + \\eta \\sum_{i=1}^{\\ell}(y_i - w_0^{(t)} - w_1^{(t)}x_i)x_i \\end{array}$$\n",
    "\n",
    "Ce calcul fonctionne très bien tant que la quantité de données n'est pas importante (nous ne discuterons pas des problèmes avec les minima locaux, les points-selle, le choix du taux d'apprentissage, des moments et d'autres choses - ces sujets sont traités en détail dans [the Numeric Computation chapter in \"Deep Learning\"](http://www.deeplearningbook.org/contents/numerical.html)).\n",
    "Il y a un problème avec la descente de gradient par lots - l'évaluation du gradient nécessite la somme d'un certain nombre de valeurs pour chaque objet de l'ensemble d'apprentissage. En d'autres termes, l'algorithme nécessite beaucoup d'itérations, et chaque itération recalcule les poids avec une formule qui contient une somme $\\sum_{i=1}^\\ell$ sur l'ensemble de l'entraînement. Que se passe-t-il lorsque nous avons des milliards d'échantillons d'entraînement?\n",
    "\n",
    "<img src=\"https://habrastorage.org/webt/ow/ng/cs/owngcs-lzoguklv1pn9vz_r4ssm.jpeg\" />\n",
    "\n",
    "D'où la motivation pour une descente de gradient stochastique! Autrement dit, nous jetons le signe de sommation et mettons à jour les poids uniquement sur des échantillons d'entraînement uniques (ou un petit nombre d'entre eux). Dans notre cas, nous avons les éléments suivants:\n",
    "\n",
    "$$\\begin{array}{rcl} w_0^{(t+1)} = w_0^{(t)} + \\eta (y_i - w_0^{(t)} - w_1^{(t)}x_i) \\\\  w_1^{(t+1)} = w_1^{(t)} + \\eta (y_i - w_0^{(t)} - w_1^{(t)}x_i)x_i \\end{array}$$\n",
    "\n",
    "Avec cette approche, rien ne garantit que nous irons dans la meilleure direction possible à chaque itération. Par conséquent, nous pouvons avoir besoin de beaucoup plus d'itérations, mais nous obtenons des mises à jour de poids beaucoup plus rapides."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Andrew Ng a fait une bonne illustration dans son [cours d'apprentissage automatique](https://www.coursera.org/learn/machine-learning). Jetons un coup d'oeil.\n",
    "\n",
    "<img src='https://habrastorage.org/files/f8d/90c/f83/f8d90cf83b044255bb07df3373f25fc7.png'>\n",
    "\n",
    "Ce sont les tracés de contour pour une fonction, et nous voulons trouver le minimum global de cette fonction. La courbe rouge montre les changements de poids (dans cette image, $\\theta_0$ et $\\theta_1$ correspondent à nos $w_0$ et $w_1$). Selon les propriétés d'un gradient, la direction du changement en chaque point est orthogonale aux courbes de niveau. Avec la descente de gradient stochastique, les poids changent de manière moins prévisible, et il peut même sembler que certaines étapes sont fausses en s'éloignant des minima; cependant, les deux procédures convergent vers la même solution."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### 1.2. Approche d'apprentissage en ligne\n",
    "La descente de gradient stochastique nous donne des conseils pratiques pour la formation des classificateurs et des régresseurs avec de grandes quantités de données jusqu'à des centaines de Go (en fonction des ressources de calcul).\n",
    "\n",
    "En considérant le cas de la régression par paires, nous pouvons stocker l'ensemble de données d'apprentissage $(X,y)$ dans le disque dur sans le charger dans la RAM (où il ne rentre tout simplement pas), lire les objets un par un et mettre à jour les poids de notre modèle:\n",
    "\n",
    "$$\\begin{array}{rcl} w_0^{(t+1)} = w_0^{(t)} + \\eta (y_i - w_0^{(t)} - w_1^{(t)}x_i) \\\\  w_1^{(t+1)} = w_1^{(t)} + \\eta (y_i - w_0^{(t)} - w_1^{(t)}x_i)x_i \\end{array}$$\n",
    "\n",
    "Après avoir travaillé sur la totalité de l'ensemble de données d'entraînement, notre fonction de perte (par exemple, l'erreur quadratique de racine carrée dans la régression ou la perte logistique dans la classification) diminuera, mais il faut généralement des dizaines de passes sur l'ensemble d'entraînement pour rendre la perte suffisamment petite.\n",
    "\n",
    "Cette approche de l'apprentissage s'appelle **apprentissage en ligne**, et ce nom est apparu avant même que les MOOC d'apprentissage automatique ne deviennent courants.\n",
    "\n",
    "Nous n'avons pas discuté ici de nombreux détails sur SGD. Si vous voulez plonger dans la théorie, je vous recommande vivement [\"Convex Optimization\" de Stephen Boyd](https://www.amazon.com/Convex-Optimization-Stephen-Boyd/dp/0521833787). Maintenant, nous allons présenter la bibliothèque Vowpal Wabbit, qui est idéale pour entraîner des modèles simples avec d'énormes ensembles de données grâce à l'optimisation stochastique et à une autre astuce, le hachage de caractéristiques."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Dans scikit-learn, les classificateurs et les régresseurs formés avec SGD sont nommés `SGDClassifier` et` SGDRegressor` dans `sklearn.linear_model`. Ce sont de belles implémentations de SGD, mais nous allons nous concentrer sur VW car il est plus performant que les modèles SGD de sklearn à bien des égards."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 2. Traitement des caractéristiques catégorielles\n",
    "\n",
    "### 2.1. Encodage d'étiquettes\n",
    "De nombreux algorithmes de classification et de régression fonctionnent dans l'espace euclidien ou métrique, ce qui implique que les données sont représentées par des vecteurs de nombres réels. Cependant, dans les données réelles, nous avons souvent des caractéristiques catégoriques avec des valeurs discrètes telles que oui / non ou janvier / février /.../ décembre. Nous verrons comment traiter ce type de données, notamment avec des modèles linéaires, et comment traiter de nombreuses caractéristiques catégorielles même lorsqu'elles ont de nombreuses valeurs uniques."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Explorons [l'ensemble de données marketing de la banque UCI](https://archive.ics.uci.edu/ml/datasets/bank+marketing) où la plupart des caractéristiques sont catégoriques."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "be6b51fd-ad36-4265-9086-fde116c6d5e4",
    "_uuid": "c8f015ed0ce62cdfc232529b9db5e17f1ebf55f5"
   },
   "outputs": [],
   "source": [
    "df = pd.read_csv(os.path.join(PATH_TO_ALL_DATA, \"bank_train.csv\"))\n",
    "labels = pd.read_csv(\n",
    "    os.path.join(PATH_TO_ALL_DATA, \"bank_train_target.csv\"), header=None\n",
    ")\n",
    "\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous pouvons voir que la plupart des caractéristiques ne sont pas représentées par des nombres. Cela pose un problème car nous ne pouvons pas utiliser la plupart des méthodes d'apprentissage automatique (du moins celles implémentées dans scikit-learn) prêtes à l'emploi.\n",
    "\n",
    "Intéressons nous à la caractéristique «éducation»."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "d5cb0f31-1e52-49a0-9bdb-ae3207541710",
    "_uuid": "136b05d444ff42d4e8521e51066bf9cd22c023fa"
   },
   "outputs": [],
   "source": [
    "df[\"education\"].value_counts().plot.barh();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La solution la plus simple consiste à mapper chaque valeur de cette fonction en un numéro unique. Par exemple, nous pouvons mapper `university.degree` à 0,` basic.9y` à 1, et ainsi de suite. Vous pouvez utiliser `sklearn.preprocessing.LabelEncoder` pour effectuer ce mappage."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "fb569cfb-711b-428d-9a29-5393dff4b348",
    "_uuid": "05c9087139ca3fcdf3b715b9b30f566bdaf0ad1c"
   },
   "outputs": [],
   "source": [
    "label_encoder = LabelEncoder()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La méthode `fit` de cette classe trouve toutes les valeurs uniques et construit le mappage réel entre les catégories et les nombres, et la méthode` transform` convertit les catégories en nombres. Après l'exécution de `fit`,` label _encoder` aura l'attribut `classes_` avec toutes les valeurs uniques de l'entité. Comptons-les pour nous assurer que la transformation était correcte."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "c692cf31-3233-4b92-8bb9-de7d7be15315",
    "_uuid": "efe26525483e1c3ba137e8f1c55f92c76ab4c799"
   },
   "outputs": [],
   "source": [
    "mapped_education = pd.Series(label_encoder.fit_transform(df[\"education\"]))\n",
    "mapped_education.value_counts().plot.barh()\n",
    "print(dict(enumerate(label_encoder.classes_)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "4658ebf0-6610-4c6f-88d0-3b1027a5c212",
    "_uuid": "68fba174918a7c498100b8bb2c4f9822a4c9a659"
   },
   "outputs": [],
   "source": [
    "df[\"education\"] = mapped_education\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Appliquons la transformation à d'autres colonnes de type `objet`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "f6b6056e-3823-4caf-a617-d99ab490512d",
    "_uuid": "8079ecf58d4fa138d92c21f46b001cb3ed050a0a"
   },
   "outputs": [],
   "source": [
    "categorical_columns = df.columns[df.dtypes == \"object\"].union([\"education\"])\n",
    "for column in categorical_columns:\n",
    "    df[column] = label_encoder.fit_transform(df[column])\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Le principal problème avec cette approche est que nous avons maintenant introduit un ordre relatif là où il pourrait ne pas exister.\n",
    "\n",
    "Par exemple, nous avons implicitement introduit l'algèbre sur les valeurs de la caractéristique **job** où nous pouvons maintenant soustraire le job du client #2 du job du client# 1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "9e85d727-a23c-4fda-859e-9cfa2491b044",
    "_uuid": "10efbea02831df6e1e8476a8813aedd227f9d4ae"
   },
   "outputs": [],
   "source": [
    "df.loc[1].job - df.loc[2].job"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Cette opération a-t-elle un sens? Pas vraiment. Essayons de former la régression logisitique avec cette transformation de caractéristique."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "7dede4d6-e787-4232-a1ac-01191a88ec88",
    "_uuid": "15c8fdb284d4e5166f3f56c4e9d8f1810186ff7c"
   },
   "outputs": [],
   "source": [
    "def logistic_regression_accuracy_on(dataframe, labels):\n",
    "    features = dataframe.as_matrix()\n",
    "    train_features, test_features, train_labels, test_labels = train_test_split(\n",
    "        features, labels\n",
    "    )\n",
    "\n",
    "    logit = LogisticRegression()\n",
    "    logit.fit(train_features, train_labels)\n",
    "    return classification_report(test_labels, logit.predict(test_features))\n",
    "\n",
    "\n",
    "print(logistic_regression_accuracy_on(df[categorical_columns], labels))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous pouvons voir que la régression logistique ne prédit jamais la classe 1. Afin d'utiliser des modèles linéaires avec des caractéristiques catégoriques, nous utiliserons une approche différente: One-Hot Encoding.\n",
    "\n",
    "### 2.2. Encodage One-Hot\n",
    "\n",
    "Supposons qu'une caractéristique puisse avoir l'une des 10 valeurs uniques. L'encodage One-Hot crée 10 nouvelles caractéristiques correspondant à ces valeurs uniques, toutes *sauf un* sont des zéros."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "df17447f-6ff8-4342-b755-c856fb1c7bd6",
    "_uuid": "6ca824f36f66c8e6889688e8f64c2ca6ec7605df"
   },
   "outputs": [],
   "source": [
    "one_hot_example = pd.DataFrame([{i: 0 for i in range(10)}])\n",
    "one_hot_example.loc[0, 6] = 1\n",
    "one_hot_example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Cette idée est implémentée dans la classe `OneHotEncoder` de` sklearn.preprocessing`. Par défaut, OneHotEncoder transforme les données en une matrice creuse (sparse matrix) pour économiser de l'espace mémoire car la plupart des valeurs sont des zéros et parce que nous ne voulons pas utiliser plus de RAM. Cependant, dans cet exemple particulier, nous ne rencontrons pas de tels problèmes, nous allons donc utiliser une représentation matricielle \"dense\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "29d1f40e-cfb4-4ee0-b9ee-8c779b7c803b",
    "_uuid": "fa7ad1f51154182f467a5df5dd7d43ec111132b8"
   },
   "outputs": [],
   "source": [
    "onehot_encoder = OneHotEncoder(sparse=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "0d70a254-f732-40c9-b738-67ca6b8d2d24",
    "_uuid": "ecfb22058b460d751779df0f9492928a317914de"
   },
   "outputs": [],
   "source": [
    "encoded_categorical_columns = pd.DataFrame(\n",
    "    onehot_encoder.fit_transform(df[categorical_columns])\n",
    ")\n",
    "encoded_categorical_columns.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous avons 53 colonnes qui correspondent au nombre de valeurs uniques des caractéristiques catégorielles dans notre ensemble de données. Lorsqu'elles sont transformées avec l'encodage One-Hot, ces données peuvent être utilisées avec des modèles linéaires:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "84c611df-8c2e-45dc-a108-aab6bf39a530",
    "_uuid": "cebd716350bc29dabb07f797db16e693fa92a76a"
   },
   "outputs": [],
   "source": [
    "print(logistic_regression_accuracy_on(encoded_categorical_columns, labels))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### 2.3. Astuce de hachage\n",
    "Les données réelles peuvent être volatiles, ce qui signifie que nous ne pouvons pas garantir que de nouvelles valeurs de caractéristiques catégorielles ne se produiront pas. Ce problème empêche l'utilisation d'un modèle entraîné sur de nouvelles données. En outre, «LabelEncoder» nécessite une analyse préliminaire de l'ensemble de données et le stockage des mappages construits en mémoire, ce qui rend difficile le travail avec de grands ensembles de données.\n",
    "\n",
    "Il existe une approche simple de la vectorisation des données catégorielles basée sur le hachage et est connue sous le nom, sans surprise, de l'astuce de hachage.\n",
    "\n",
    "Les fonctions de hachage peuvent nous aider à trouver des codes uniques pour différentes valeurs de caractéristiques, par exemple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "d9eb2e54-18e7-4056-8f28-f22a044596d8",
    "_uuid": "01064911e1e0af0b3829b0e0be2a965aa3e8eb3e"
   },
   "outputs": [],
   "source": [
    "for s in (\"university.degree\", \"high.school\", \"illiterate\"):\n",
    "    print(s, \"->\", hash(s))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous n'utiliserons pas de valeurs négatives ou de valeurs de grande amplitude, nous limitons donc la plage de valeurs pour la fonction de hachage:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "943fc564-80f0-4350-980a-cce6c6a2e7cd",
    "_uuid": "b92f7eabc498b8339abed0f8fd400bba5750209a"
   },
   "outputs": [],
   "source": [
    "hash_space = 25\n",
    "for s in (\"university.degree\", \"high.school\", \"illiterate\"):\n",
    "    print(s, \"->\", hash(s) % hash_space)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Imaginons que notre ensemble de données contienne un étudiant célibataire (c'est-à-dire non marié), qui a reçu un appel lundi. Ses vecteurs de caractéristiques seront créés de la même manière que dans le cas de l'encodage One-Hot, mais dans l'espace avec une plage fixe pour toutes les caractéristiques:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "c94253b2-3981-4bbd-817f-96c3f660d464",
    "_uuid": "af62a2ed3770a537bc3168d0a06c6d20b737f887"
   },
   "outputs": [],
   "source": [
    "hashing_example = pd.DataFrame([{i: 0.0 for i in range(hash_space)}])\n",
    "for s in (\"job=student\", \"marital=single\", \"day_of_week=mon\"):\n",
    "    print(s, \"->\", hash(s) % hash_space)\n",
    "    hashing_example.loc[0, hash(s) % hash_space] = 1\n",
    "hashing_example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous tenons à souligner que nous hachons non seulement les valeurs de caractéristique, mais également des paires de **nom de caractéristique + valeur de caractéristique**. Il est important de le faire afin que nous puissions distinguer les mêmes valeurs de différentes caractéristiques."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "48540748-405a-413d-ac0c-5aa0422b9087",
    "_uuid": "e025f8486b3091090e93c27c082145b5f3e11967"
   },
   "outputs": [],
   "source": [
    "assert hash(\"no\") == hash(\"no\")\n",
    "assert hash(\"housing=no\") != hash(\"loan=no\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Est-il possible d'avoir une collision lors de l'utilisation de codes de hachage? Bien sûr, c'est possible, mais c'est un cas rare avec des espaces de hachage suffisamment grands. Même en cas de collision, les métriques de régression ou de classification n'en souffriront pas beaucoup. Dans ce cas, les collisions de hachage fonctionnent comme une forme de régularisation.\n",
    "\n",
    "\n",
    "<img src=\"https://habrastorage.org/webt/4o/wx/59/4owx59vdvwc9mzrf81t2fa2rqrc.jpeg\">\n",
    "\n",
    "Vous dites peut-être \"WTF?\"; le hachage semble contre-intuitif. C'est vrai, mais ces heuristiques sont parfois, en fait, la seule approche plausible pour travailler avec des données catégorielles (que pouvez-vous faire d'autre si vous avez 30 millions de caractéristiques?). De plus, cette technique s'est avérée efficace. Au fur et à mesure que vous travaillez avec des données, vous pouvez le constater par vous-même.\n",
    "\n",
    "Une bonne analyse des collisions de hachage, de leur dépendance à l'espace des caractéristiques et des dimensions de l'espace de hachage et affectant les performances de classification / régression est effectuée dans [cet article](https://booking.ai/dont-be-tricked-by-the-hashing- trick-192a6aae3087) par Booking.com."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 3. Vowpal Wabbit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "[Vowpal Wabbit](https://github.com/JohnLangford/vowpal_wabbit)(VW) est l'une des bibliothèques d'apprentissage automatique les plus répandues dans l'industrie. Il se distingue par sa vitesse d'entraînement et sa prise en charge de nombreux modes d'entraînement, en particulier pour l'apprentissage en ligne avec des données volumineuses et de haute dimension. C'est l'un des principaux mérites de la bibliothèque. De plus, avec l'astuce de hachage implémentée, Vowpal Wabbit est un choix parfait pour travailler avec des données textuelles.\n",
    "\n",
    "Shell est l'interface principale de VW."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "24fd028f-416e-43ff-9530-5c8f47bde176",
    "_uuid": "4cc17ec6551c7e7dcf224a3b03794720d90cb966"
   },
   "outputs": [],
   "source": [
    "!vw --help"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Vowpal Wabbit lit les données des fichiers ou du flux d'entrée standard (stdin) avec le format suivant:\n",
    "\n",
    "`[Label] [Importance] [Tag]|Namespace Features |Namespace Features ... |Namespace Features`\n",
    "\n",
    "`Namespace=String[:Value]`\n",
    "\n",
    "`Features=(String[:Value] )*`\n",
    "\n",
    "[] désigne des éléments non obligatoires et (...)* signifie plusieurs entrées autorisées.\n",
    "\n",
    "- **Label** est un nombre. Dans le cas de la classification, il vaut généralement 1 et -1; pour la régression, c'est une vraie valeur flottante\n",
    "- **Importance** est un nombre. Il indique le poids de l'échantillon pendant l'entraînement. Cette configuration aide lorsque vous travaillez avec des données déséquilibrées.\n",
    "- **Tag** est une chaîne sans espaces. C'est le \"nom\" de l'échantillon que VW enregistre lors de la prédiction. Afin de séparer Tag de Importance, il est préférable de commencer Tag avec le caractère '.\n",
    "- **Namespace** sert à créer différents espaces de caractéristiques.\n",
    "- **Les caractéristiques** sont des caractéristiques d'objet à l'intérieur d'un **espace de noms** donné. Les caractéristiques ont un poids de 1,0 par défaut, mais il peut être modifié, par exemple caractéristique: 0,1.\n",
    "\n",
    "\n",
    "La chaîne suivante correspond au format VW:\n",
    "\n",
    "```\n",
    "1 1.0 |Subject WHAT car is this |Organization University of Maryland:0.5 College Park\n",
    "```\n",
    "\n",
    "\n",
    "Vérifions le format en exécutant VW avec cet exemple d'entraînement:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "5cbedd43-8d8e-4577-9a67-4f2879f0666c",
    "_uuid": "250fa571012a53fbe15f9b16bd88244dae4f805a"
   },
   "outputs": [],
   "source": [
    "! echo '1 1.0 |Subject WHAT car is this |Organization University of Maryland:0.5 College Park' | vw"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "VW est un outil formidable pour travailler avec des données textuelles. Nous allons l'illustrer avec le jeu de données [20newsgroups](http://scikit-learn.org/stable/datasets/twenty_newsgroups.html), qui contient des articles de 20 newsletters différentes.\n",
    "\n",
    "\n",
    "### 3.1. Articles. Classification binaire."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "a37611a4-5bdb-40bb-9b89-bfddd8726ba9",
    "_uuid": "0f527d54e1a759be5fb53665ae4b768116094b27"
   },
   "outputs": [],
   "source": [
    "# load data with sklearn's function\n",
    "newsgroups = fetch_20newsgroups(PATH_TO_ALL_DATA)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "7fad47bd-c5fc-4b56-b024-0e866137d07b",
    "_uuid": "a582c83f4d792d71d12e5063d7539c040b017a67"
   },
   "outputs": [],
   "source": [
    "newsgroups[\"target_names\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Regardons le premier document de cette collection:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "d571d3d0-fba9-4d2f-a4a9-3a36e80fe456",
    "_uuid": "039c32b72a84277884c788de8fe2a5e692a4dfb5"
   },
   "outputs": [],
   "source": [
    "text = newsgroups[\"data\"][0]\n",
    "target = newsgroups[\"target_names\"][newsgroups[\"target\"][0]]\n",
    "\n",
    "print(\"-----\")\n",
    "print(target)\n",
    "print(\"-----\")\n",
    "print(text.strip())\n",
    "print(\"----\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Maintenant, nous convertissons les données en quelque chose que Vowpal Wabbit peut comprendre. Nous rejetterons les mots de moins de 3 symboles. Ici, nous allons sauter certaines étapes importantes du NLP telles que le stemming et la lemmatisation; cependant, nous verrons plus tard que VW résout le problème même sans ces étapes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "61c25a92-1b0a-45cf-904f-6c35de89575f",
    "_uuid": "9b480cff8801ad51f371b05f6f63225c758883ca"
   },
   "outputs": [],
   "source": [
    "def to_vw_format(document, label=None):\n",
    "    return (\n",
    "        str(label or \"\")\n",
    "        + \" |text \"\n",
    "        + \" \".join(re.findall(\"\\w{3,}\", document.lower()))\n",
    "        + \"\\n\"\n",
    "    )\n",
    "\n",
    "\n",
    "to_vw_format(text, 1 if target == \"rec.autos\" else -1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous divisons l'ensemble de données en **train** et **test** et les écrivons dans des fichiers séparés. Nous considérerons un document comme positif s'il correspond à **rec.autos**. Ainsi, nous construisons un modèle qui distingue les articles sur les voitures d'autres sujets:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "297c4ed7-3157-412e-b3af-1bc59084defd",
    "_uuid": "f5754e324f2165a47ab603e2e5fafa5b681ea1e9"
   },
   "outputs": [],
   "source": [
    "all_documents = newsgroups[\"data\"]\n",
    "all_targets = [\n",
    "    1 if newsgroups[\"target_names\"][target] == \"rec.autos\" else -1\n",
    "    for target in newsgroups[\"target\"]\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "d0cd644c-fdd2-4b46-9ff5-377ef12a3d6e",
    "_uuid": "623b5b239dda745dc0e79a7b225825eed8f98d09"
   },
   "outputs": [],
   "source": [
    "train_documents, test_documents, train_labels, test_labels = train_test_split(\n",
    "    all_documents, all_targets, random_state=7\n",
    ")\n",
    "\n",
    "with open(os.path.join(PATH_TO_ALL_DATA, \"20news_train.vw\"), \"w\") as vw_train_data:\n",
    "    for text, target in zip(train_documents, train_labels):\n",
    "        vw_train_data.write(to_vw_format(text, target))\n",
    "with open(os.path.join(PATH_TO_ALL_DATA, \"20news_test.vw\"), \"w\") as vw_test_data:\n",
    "    for text in test_documents:\n",
    "        vw_test_data.write(to_vw_format(text))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Maintenant, nous transmettons le fichier de formation créé à Vowpal Wabbit. Nous résolvons le problème de classification avec une fonction de perte charnière (SVM linéaire). Le modèle entraîné sera enregistré dans le fichier `20news_model.vw`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "80f2960a-2389-4810-9b04-1928b613b4b2",
    "_uuid": "7a5a15cc11e801a98b493bab026136c9eeb33816"
   },
   "outputs": [],
   "source": [
    "!vw -d $PATH_TO_ALL_DATA/20news_train.vw \\\n",
    " --loss_function hinge -f $PATH_TO_ALL_DATA/20news_model.vw"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "VW imprime beaucoup d'informations intéressantes pendant l'entraînement (on peut les supprimer avec le paramètre `--quiet`). Vous pouvez voir la documentation de la sortie de diagnostic sur [GitHub](https://github.com/JohnLangford/vowpal_wabbit/wiki/Tutorial#vws-diagnostic-information). Notez comment la perte moyenne diminue pendant l'entraînement. Pour le calcul des pertes, VW utilise des échantillons qu'il n'a jamais vus auparavant, donc cette mesure est généralement précise. Maintenant, nous appliquons notre modèle entraîné à l'ensemble de test, en enregistrant les prédictions dans un fichier avec l'indicateur `-p`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "17c99751-4e30-4251-b18f-fe6ed8a34e58",
    "_uuid": "276f1431b121543e67f668bb5e9d601dd882e98e"
   },
   "outputs": [],
   "source": [
    "!vw -i $PATH_TO_ALL_DATA/20news_model.vw -t -d $PATH_TO_ALL_DATA/20news_test.vw \\\n",
    "-p $PATH_TO_ALL_DATA/20news_test_predictions.txt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Maintenant, nous chargeons nos prédictions, calculons AUC et traçons la courbe ROC:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "93d9f296-f06b-4d18-94a5-e18ccf88b094",
    "_uuid": "40b4112f1afa764db10b58b0ab05adfed48ab388"
   },
   "outputs": [],
   "source": [
    "with open(os.path.join(PATH_TO_ALL_DATA, \"20news_test_predictions.txt\")) as pred_file:\n",
    "    test_prediction = [float(label) for label in pred_file.readlines()]\n",
    "\n",
    "auc = roc_auc_score(test_labels, test_prediction)\n",
    "roc_curve = roc_curve(test_labels, test_prediction)\n",
    "\n",
    "with plt.xkcd():\n",
    "    plt.plot(roc_curve[0], roc_curve[1])\n",
    "    plt.plot([0, 1], [0, 1])\n",
    "    plt.xlabel(\"FPR\")\n",
    "    plt.ylabel(\"TPR\")\n",
    "    plt.title(\"test AUC = %f\" % (auc))\n",
    "    plt.axis([-0.05, 1.05, -0.05, 1.05]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La valeur AUC que nous obtenons montre que nous avons atteint une qualité de classification élevée."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### 3.2. Articles. Classification multiclasse"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous utiliserons le même jeu de données d'actualités, mais, cette fois, nous allons résoudre un problème de classification multiclasse. «Vowpal Wabbit» est un peu pointilleux - il veut des étiquettes commençant de 1 à K, où K - est le nombre de classes dans la tâche de classification (20 dans notre cas). Nous allons donc utiliser LabelEncoder et ajouter 1 par la suite (rappelez-vous que `LabelEncoder` mappe les étiquettes dans la plage de 0 à K-1)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "e94dfe85-61d6-42d2-b19e-89923b511535",
    "_uuid": "30fca7585b9d841b5925ec9b3db9b12ff1bb5142"
   },
   "outputs": [],
   "source": [
    "all_documents = newsgroups[\"data\"]\n",
    "topic_encoder = LabelEncoder()\n",
    "all_targets_mult = topic_encoder.fit_transform(newsgroups[\"target\"]) + 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "**Les données sont les mêmes, mais nous avons changé les étiquettes, train_labels_mult et test_labels_mult, en vecteurs d'étiquettes de 1 à 20.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "90e22502-efe9-4a2c-9868-fa6a18ed91b2",
    "_uuid": "f90ea2c247690e680d26f6d30992daf45ffd0315"
   },
   "outputs": [],
   "source": [
    "train_documents, test_documents, train_labels_mult, test_labels_mult = train_test_split(\n",
    "    all_documents, all_targets_mult, random_state=7\n",
    ")\n",
    "\n",
    "with open(os.path.join(PATH_TO_ALL_DATA, \"20news_train_mult.vw\"), \"w\") as vw_train_data:\n",
    "    for text, target in zip(train_documents, train_labels_mult):\n",
    "        vw_train_data.write(to_vw_format(text, target))\n",
    "with open(os.path.join(PATH_TO_ALL_DATA, \"20news_test_mult.vw\"), \"w\") as vw_test_data:\n",
    "    for text in test_documents:\n",
    "        vw_test_data.write(to_vw_format(text))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous entraînons Vowpal Wabbit en mode de classification multiclasse, en passant le paramètre `oaa` (\" un contre tous \") avec le nombre de classes. Voyons également de quels paramètres la qualité de notre modèle dépend (plus d'informations peuvent être trouvées dans le [tutoriel officiel de Vowpal Wabbit](https://github.com/JohnLangford/vowpal_wabbit/wiki/Tutorial)):\n",
    " - learning rate (-l, 0,5 par défaut) - taux de changement de poids à chaque pas\n",
    " - learning rate decay (--power_ t, 0.5 par défaut) - il est prouvé en pratique que, si le taux d'apprentissage diminue avec le nombre de pas en descente de gradient stochastique, on approche mieux la perte minimale\n",
    " - loss function (fonction --loss _) - tout l'algorithme d'apprentissage en dépend. Voir [docs](https://github.com/JohnLangford/vowpal_wabbit/wiki/Loss-functions) pour les fonctions de perte\n",
    " - Regularization (-l1) - notez que VW calcule la régularisation pour chaque objet. C'est pourquoi nous définissons généralement les valeurs de régularisation à environ $10^{-20}.$\n",
    " \n",
    "De plus, nous pouvons essayer le réglage automatique des paramètres Vowpal Wabbit avec [Hyperopt](https://github.com/hyperopt/hyperopt)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "80ae364e-8332-41db-bdc3-7f642bb24d7a",
    "_uuid": "97ea9ebcb6ed46b5512f5a9364680e501c2925cf"
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "!vw --oaa 20 $PATH_TO_ALL_DATA/20news_train_mult.vw -f $PATH_TO_ALL_DATA/20news_model_mult.vw \\\n",
    "--loss_function=hinge"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "4d78df8b-f6d2-477a-b5be-a5f7364c0e56",
    "_uuid": "d30d194b2be9521d609d703385f528c24a557aa0"
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "!vw -i $PATH_TO_ALL_DATA/20news_model_mult.vw -t -d $PATH_TO_ALL_DATA/20news_test_mult.vw \\\n",
    "-p $PATH_TO_ALL_DATA/20news_test_predictions_mult.txt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "bf4c66cf-3039-4ac4-a129-e52652c16d8d",
    "_uuid": "f6911717662ade8c466cc2f3681f356619d8676c"
   },
   "outputs": [],
   "source": [
    "with open(\n",
    "    os.path.join(PATH_TO_ALL_DATA, \"20news_test_predictions_mult.txt\")\n",
    ") as pred_file:\n",
    "    test_prediction_mult = [float(label) for label in pred_file.readlines()]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "c8cd9521-6c23-412b-959d-19e9f95bd5ee",
    "_uuid": "c110cfb692d0c280009ca815efa504f5889e28a6"
   },
   "outputs": [],
   "source": [
    "accuracy_score(test_labels_mult, test_prediction_mult)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Voici à quelle fréquence le modèle classe mal l'athéisme avec d'autres sujets:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "5b40bd39-4895-4713-be3a-2fea6fa37062",
    "_uuid": "fefe7d01f98e8cd3af7ce5a3b7d0f859755e5047"
   },
   "outputs": [],
   "source": [
    "M = confusion_matrix(test_labels_mult, test_prediction_mult)\n",
    "for i in np.where(M[0, :] > 0)[0][1:]:\n",
    "    print(newsgroups[\"target_names\"][i], M[0, i])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### 3.3. Critiques de films IMDB\n",
    "Dans cette partie, nous ferons la classification binaire des critiques de films [IMDB](http://www.imdb.com) (International Movie DataBase). Nous verrons à quelle vitesse Vowpal Wabbit fonctionne.\n",
    "\n",
    "En utilisant la fonction `load_files` de` sklearn.datasets`, nous chargeons les ensembles de données des critiques de films. C'est le même jeu de données que nous avons utilisé dans le notebook topic04 part4."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "a1827aa1-5bc7-4838-bb01-68792de451b6",
    "_uuid": "e719b5b6c976cf9daf10a9f3b7c35b4cde79b057"
   },
   "outputs": [],
   "source": [
    "import tarfile\n",
    "\n",
    "# Download the dataset if not already in place\n",
    "from io import BytesIO\n",
    "\n",
    "import requests\n",
    "\n",
    "url = \"http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz\"\n",
    "\n",
    "\n",
    "def load_imdb_dataset(extract_path=\"../../data\", overwrite=False):\n",
    "    # check if existed already\n",
    "    if (\n",
    "        os.path.isfile(os.path.join(extract_path, \"aclImdb\", \"README\"))\n",
    "        and not overwrite\n",
    "    ):\n",
    "        print(\"IMDB dataset is already in place.\")\n",
    "        return\n",
    "\n",
    "    print(\"Downloading the dataset from:  \", url)\n",
    "    response = requests.get(url)\n",
    "\n",
    "    tar = tarfile.open(mode=\"r:gz\", fileobj=BytesIO(response.content))\n",
    "\n",
    "    data = tar.extractall(extract_path)\n",
    "\n",
    "\n",
    "load_imdb_dataset()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Lire les données du train, séparer les étiquettes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "PATH_TO_IMDB = \"../../data/aclImdb\"\n",
    "\n",
    "reviews_train = load_files(\n",
    "    os.path.join(PATH_TO_IMDB, \"train\"), categories=[\"pos\", \"neg\"]\n",
    ")\n",
    "\n",
    "text_train, y_train = reviews_train.data, reviews_train.target"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "db550541-630a-47a5-8b98-246d94a3fb33",
    "_uuid": "593032d9be6e7aff583e0c7ba40e3e801b136edf"
   },
   "outputs": [],
   "source": [
    "print(\"Number of documents in training data: %d\" % len(text_train))\n",
    "print(np.bincount(y_train))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Faire de même pour l'ensemble de test."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "ce60fd02-e5d4-40aa-a5db-33556ef7063f",
    "_uuid": "8fea469dd838a26671f234d54cf82d6f57188e73"
   },
   "outputs": [],
   "source": [
    "reviews_test = load_files(os.path.join(PATH_TO_IMDB, \"test\"), categories=[\"pos\", \"neg\"])\n",
    "text_test, y_test = reviews_test.data, reviews_test.target"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Number of documents in test data: %d\" % len(text_test))\n",
    "print(np.bincount(y_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Jetez un œil à des exemples d'avis et leurs libellés correspondants."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "213017e1-25f0-4d08-8d9c-5341b449b8f6",
    "_uuid": "aea5acfcef80c4006dbcab233d237d2cd091ab48"
   },
   "outputs": [],
   "source": [
    "text_train[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "23c8ca4d-7287-406a-8381-f9c9c095f1b7",
    "_uuid": "976c29d60e5fc1da27e36b5c34dd1fa4185cd083"
   },
   "outputs": [],
   "source": [
    "y_train[0]  # good review"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "b4cd5590-5952-4388-9960-37702e78be4f",
    "_uuid": "2fe24a84eb18599fb67664a63e3b2178ee29ee26"
   },
   "outputs": [],
   "source": [
    "text_train[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "0faa88f7-c96d-4073-9b16-b6d68d31d354",
    "_uuid": "f64122c2e93648b75494df26b1de4bf01358df35"
   },
   "outputs": [],
   "source": [
    "y_train[1]  # bad review"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "23032bfe-25d1-4f79-b24e-a4cb69f44127",
    "_uuid": "0ce9e75db41712f6f9161f9d6cd8000381a5b3ba"
   },
   "outputs": [],
   "source": [
    "to_vw_format(str(text_train[1]), 1 if y_train[0] == 1 else -1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Maintenant, nous préparons l'apprentissage (`movie _reviews_ train.vw`), la validation (` movie _reviews_ valid.vw`) et le test (`movie _reviews_ test.vw`) définit pour Vowpal Wabbit. Nous utiliserons 70% pour la formation, 30% pour le reste."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "c08edd2b-7290-4e70-9072-895e33923d44",
    "_uuid": "7309769413ef059831390aea012334a5d7482817"
   },
   "outputs": [],
   "source": [
    "train_share = int(0.7 * len(text_train))\n",
    "train, valid = text_train[:train_share], text_train[train_share:]\n",
    "train_labels, valid_labels = y_train[:train_share], y_train[train_share:]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "a30cddd4-fceb-46ca-87a8-1b7b7d3b36d1",
    "_uuid": "9154377a268f463e98c17432a84a77d03e68052e"
   },
   "outputs": [],
   "source": [
    "len(train_labels), len(valid_labels)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "6d25382f-b135-47f8-a82c-2568ad02f9ff",
    "_uuid": "cce438177377598771a890dc94651de5a6a8495e"
   },
   "outputs": [],
   "source": [
    "with open(\n",
    "    os.path.join(PATH_TO_ALL_DATA, \"movie_reviews_train.vw\"), \"w\"\n",
    ") as vw_train_data:\n",
    "    for text, target in zip(train, train_labels):\n",
    "        vw_train_data.write(to_vw_format(str(text), 1 if target == 1 else -1))\n",
    "with open(\n",
    "    os.path.join(PATH_TO_ALL_DATA, \"movie_reviews_valid.vw\"), \"w\"\n",
    ") as vw_train_data:\n",
    "    for text, target in zip(valid, valid_labels):\n",
    "        vw_train_data.write(to_vw_format(str(text), 1 if target == 1 else -1))\n",
    "with open(os.path.join(PATH_TO_ALL_DATA, \"movie_reviews_test.vw\"), \"w\") as vw_test_data:\n",
    "    for text in text_test:\n",
    "        vw_test_data.write(to_vw_format(str(text)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "cb999742-8ad7-428d-8f66-f015d38de4f8",
    "_uuid": "8d87247a0d044e81b545e37ffaa59a96712a252f"
   },
   "outputs": [],
   "source": [
    "!head -2 $PATH_TO_ALL_DATA/movie_reviews_train.vw"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "f25cd0d3-1767-41f1-b7bf-096224619bc5",
    "_uuid": "b63440f7ffebc472ecbe54d8e3aef8c3a3b75fa1"
   },
   "outputs": [],
   "source": [
    "!head -2 $PATH_TO_ALL_DATA/movie_reviews_valid.vw"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "eaa1a658-e6e0-42f1-9c30-14be762fc7d9",
    "_uuid": "fd4e364bad19a64110f7ec839a56a2776222306c"
   },
   "outputs": [],
   "source": [
    "!head -2 $PATH_TO_ALL_DATA/movie_reviews_test.vw"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "**Nous lançons maintenant Vowpal Wabbit avec les arguments suivants:**\n",
    "\n",
    " - `-d`, chemin vers l'ensemble d'entraînement (fichier .vw correspondant)\n",
    " - `--loss_function` - charnière (n'hésitez pas à expérimenter ici)\n",
    " - `-f` - chemin vers le fichier de sortie (qui peut également être au format .vw)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "89094f9e-7453-4137-87b0-21df78627ae6",
    "_uuid": "4e27dea292bfde2224e802d60c00aad0fbac3274"
   },
   "outputs": [],
   "source": [
    "!vw -d $PATH_TO_ALL_DATA/movie_reviews_train.vw --loss_function hinge \\\n",
    "-f $PATH_TO_ALL_DATA/movie_reviews_model.vw --quiet"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Ensuite, faites la prédiction de hold-out avec les arguments VW suivants:\n",
    " - `-i` - chemin vers le modèle entraîné (fichier .vw)\n",
    " - `-d` - chemin vers le jeu d'exclusion (fichier .vw)\n",
    " - `-p` - chemin vers un fichier txt où les prédictions seront stockées\n",
    " - `-t` - dit à VW d'ignorer les étiquettes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "7fbac967-4a81-4eff-9d31-721a197ed568",
    "_uuid": "c8ae7e58aeacc3e88910b62aff684ab12fb970e1"
   },
   "outputs": [],
   "source": [
    "!vw -i $PATH_TO_ALL_DATA/movie_reviews_model.vw -t \\\n",
    "-d $PATH_TO_ALL_DATA/movie_reviews_valid.vw -p $PATH_TO_ALL_DATA/movie_valid_pred.txt --quiet"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Lisez les prédictions du fichier texte et estimez la précision et l'AUC ROC. Notez que VW imprime les estimations de probabilité de la classe +1. Ces estimations sont distribuées de -1 à 1, nous pouvons donc les convertir en réponses binaires, en supposant que les valeurs positives appartiennent à la classe 1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "609497c1-de20-436d-9910-018316f36e8e",
    "_uuid": "8cce125db3a615ff84b0892819f1b5f7c680a3e2"
   },
   "outputs": [],
   "source": [
    "with open(os.path.join(PATH_TO_ALL_DATA, \"movie_valid_pred.txt\")) as pred_file:\n",
    "    valid_prediction = [float(label) for label in pred_file.readlines()]\n",
    "print(\n",
    "    \"Accuracy: {}\".format(\n",
    "        round(\n",
    "            accuracy_score(\n",
    "                valid_labels, [int(pred_prob > 0) for pred_prob in valid_prediction]\n",
    "            ),\n",
    "            3,\n",
    "        )\n",
    "    )\n",
    ")\n",
    "print(\"AUC: {}\".format(round(roc_auc_score(valid_labels, valid_prediction), 3)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Encore une fois, faites de même pour l'ensemble de test."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "aaa8c5c7-aa05-4cdd-a06e-bf7325972691",
    "_uuid": "36f5d2d1336d537c95947e884207ecffe18e2d91"
   },
   "outputs": [],
   "source": [
    "!vw -i $PATH_TO_ALL_DATA/movie_reviews_model.vw -t \\\n",
    "-d $PATH_TO_ALL_DATA/movie_reviews_test.vw \\\n",
    "-p $PATH_TO_ALL_DATA/movie_test_pred.txt --quiet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "7a61e739-89f4-44d1-a290-9ad2be4bdffd",
    "_uuid": "b3cf5f3efa4dbaf05baff4f4900c61665f2d9fee"
   },
   "outputs": [],
   "source": [
    "with open(os.path.join(PATH_TO_ALL_DATA, \"movie_test_pred.txt\")) as pred_file:\n",
    "    test_prediction = [float(label) for label in pred_file.readlines()]\n",
    "print(\n",
    "    \"Accuracy: {}\".format(\n",
    "        round(\n",
    "            accuracy_score(\n",
    "                y_test, [int(pred_prob > 0) for pred_prob in test_prediction]\n",
    "            ),\n",
    "            3,\n",
    "        )\n",
    "    )\n",
    ")\n",
    "print(\"AUC: {}\".format(round(roc_auc_score(y_test, test_prediction), 3)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Essayons d'obtenir une plus grande précision en incorporant des bigrammes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "c5b25dce-c730-4031-8938-1b9fa64575b6",
    "_uuid": "98fbb3a6f858b362aef8f797469075eec9072aaf"
   },
   "outputs": [],
   "source": [
    "!vw -d $PATH_TO_ALL_DATA/movie_reviews_train.vw \\\n",
    "--loss_function hinge --ngram 2 -f $PATH_TO_ALL_DATA/movie_reviews_model2.vw --quiet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "17ceb519-ab1f-4184-b9c2-9eb925b32ea8",
    "_uuid": "48aa4c0a6c58be2781ed607a2393de37083345ae"
   },
   "outputs": [],
   "source": [
    "!vw -i$PATH_TO_ALL_DATA/movie_reviews_model2.vw -t -d $PATH_TO_ALL_DATA/movie_reviews_valid.vw \\\n",
    "-p $PATH_TO_ALL_DATA/movie_valid_pred2.txt --quiet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "d1c01554-e2b6-4a55-97b9-0d17c8b26686",
    "_uuid": "be78a33c70c03e1bc9a48a25e804248930fba2c2"
   },
   "outputs": [],
   "source": [
    "with open(os.path.join(PATH_TO_ALL_DATA, \"movie_valid_pred2.txt\")) as pred_file:\n",
    "    valid_prediction = [float(label) for label in pred_file.readlines()]\n",
    "print(\n",
    "    \"Accuracy: {}\".format(\n",
    "        round(\n",
    "            accuracy_score(\n",
    "                valid_labels, [int(pred_prob > 0) for pred_prob in valid_prediction]\n",
    "            ),\n",
    "            3,\n",
    "        )\n",
    "    )\n",
    ")\n",
    "print(\"AUC: {}\".format(round(roc_auc_score(valid_labels, valid_prediction), 3)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "3015a9e6-f814-4beb-9d37-f8c6a2e2c546",
    "_uuid": "f1aa2121a7ee92577ad4058b0688f8290d24e1d5"
   },
   "outputs": [],
   "source": [
    "!vw -i $PATH_TO_ALL_DATA/movie_reviews_model2.vw -t -d $PATH_TO_ALL_DATA/movie_reviews_test.vw \\\n",
    "-p $PATH_TO_ALL_DATA/movie_test_pred2.txt --quiet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "_cell_guid": "527e0327-b208-47a0-9853-2e1bd06beb38",
    "_uuid": "22b7b6feb376fcbd44faf942914afaade9368b30"
   },
   "outputs": [],
   "source": [
    "with open(os.path.join(PATH_TO_ALL_DATA, \"movie_test_pred2.txt\")) as pred_file:\n",
    "    test_prediction2 = [float(label) for label in pred_file.readlines()]\n",
    "print(\n",
    "    \"Accuracy: {}\".format(\n",
    "        round(\n",
    "            accuracy_score(\n",
    "                y_test, [int(pred_prob > 0) for pred_prob in test_prediction2]\n",
    "            ),\n",
    "            3,\n",
    "        )\n",
    "    )\n",
    ")\n",
    "print(\"AUC: {}\".format(round(roc_auc_score(y_test, test_prediction2), 3)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "L'ajout de bigrammes a vraiment aidé à améliorer notre modèle!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### 3.4. Classer des gigaoctets de questions StackOverflow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Cette section a été déplacée vers Kaggle, veuillez explorer [ce noyau](https://www.kaggle.com/kashnitsky/topic-8-online-learning-and-vowpal-wabbit)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 4. Mission de démonstration\n",
    "Pour mieux comprendre l'apprentissage stochastique, vous pouvez effectuer [cette tâche](https://www.kaggle.com/kashnitsky/assignment-8-implementing-online-regressor) où il vous sera demandé d'implémenter un régresseur de gradient stochastique à partir de zéro . La mission est juste pour que vous vous entraîniez, et va avec une [solution](https://www.kaggle.com/kashnitsky/a8-demo-implementing-online-regressor-solution).\n",
    "\n",
    "## 5. Ressources utiles\n",
    "- The same notebook as am interactive web-based [Kaggle Kernel](https://www.kaggle.com/kashnitsky/topic-8-online-learning-and-vowpal-wabbit)\n",
    "- [\"Training while reading\"](https://www.kaggle.com/kashnitsky/training-while-reading-vowpal-wabbit-starter) - an example of the Python wrapper usage\n",
    "- Main course [site](https://mlcourse.ai), [course repo](https://github.com/Yorko/mlcourse.ai), and YouTube [channel](https://www.youtube.com/watch?v=QKTuw4PNOsU&list=PLVlY_7IJCMJeRfZ68eVfEcu-UcN9BbwiX)\n",
    "- Course materials as a [Kaggle Dataset](https://www.kaggle.com/kashnitsky/mlcourse)\n",
    "- Official VW [documentation](https://github.com/JohnLangford/vowpal_wabbit/wiki) on Github\n",
    "- [\"Awesome Vowpal Wabbit\"](https://github.com/VowpalWabbit/vowpal_wabbit/wiki/Awesome-Vowpal-Wabbit) Wiki\n",
    "- [Don’t be tricked by the Hashing Trick](https://booking.ai/dont-be-tricked-by-the-hashing-trick-192a6aae3087) - analysis of hash collisions, their dependency on feature space and hashing space dimensions and affecting classification/regression performance\n",
    "- [\"Numeric Computation\" Chapter](http://www.deeplearningbook.org/contents/numerical.html) of the [Deep Learning book](http://www.deeplearningbook.org/)\n",
    "- [\"Convex Optimization\" by Stephen Boyd](https://www.amazon.com/Convex-Optimization-Stephen-Boyd/dp/0521833787)\n",
    "- \"Command-line Tools can be 235x Faster than your Hadoop Cluster\" [post](https://aadrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html)\n",
    "- Benchmarking various ML algorithms on Criteo 1TB dataset on [GitHub](https://github.com/rambler-digital-solutions/criteo-1tb-benchmark)\n",
    "- [VW on FastML.com](http://fastml.com/blog/categories/vw/)"
   ]
  }
 ],
 "metadata": {
  "hide_input": false,
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  },
  "nbTranslate": {
   "displayLangs": [
    "*"
   ],
   "hotkey": "alt-t",
   "langInMainMenu": true,
   "sourceLang": "en",
   "targetLang": "fr",
   "useGoogleTranslate": true
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
